#include "../aux.h"
#include "server.h"

int serverFifo;
int serverStatus;
int fd;
unsigned int lastID;
vec* users;

char *shm;
int shmfd;
char SEM_NAME[] = "/sem1";
char SHM_NAME[] = "/shm1";
sem_t *sem;;

/**
 * The main function
 */
int main() {

	lastID = 0;
	users = loadUsers(USERS_FILE, &lastID);

	if (mkfifo("requests",0660)<0)
		if (errno==EEXIST)
			printf("FIFO 'requests' already exists.\n");
			else
				printf("Can't create FIFO.\n");
		else
		printf("FIFO 'requests' successfully created!\n");

	if ((fd=open("requests",O_RDWR)) !=-1)
		printf("FIFO 'requests' opened.\n");

	//Create the shared memory region
	shmfd = shm_open(SHM_NAME,O_CREAT|O_RDWR,0600);
	if(shmfd<0)
	{
		perror("READER failure in shm_open()");
		exit(1);
	}

	shm = (char *) mmap(0,SHM_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,shmfd,0);
	if(shm == MAP_FAILED)
	{
		perror("READER failure in mmap()");
		exit(2);
	}
	sem = sem_open(SEM_NAME,O_CREAT,0600,0);
	if(sem == SEM_FAILED)
	{
		perror("WRITER failure in sem_open()");
		exit(4);
	}
	sem_post(sem);

	serverStatus=SERVER_ON;

	while(1){
		printf("Server on! Waiting for requests...\n");

		Request* req = malloc(sizeof(Request));
		read(fd,req,sizeof(Request));

		// Server shutdown
		if(serverStatus==SERVER_OFF){
			if(req->type == Shutdown){
				unlink("requests");
				exit(0);
			}
		}

		pthread_t tid;

		switch (req->type)
		{

			case Add_Account:
			pthread_create (&tid, NULL ,AddAccount, req);
			break;

			case Remove_Account:
			pthread_create (&tid, NULL ,RemoveAccount, req);
			break;

			case Withdraw:
			pthread_create (&tid, NULL ,UserWithdraw, req);
			break;

			case Deposit:
			pthread_create (&tid, NULL ,UserDeposit, req);
			break;

			case CheckBalance:
			pthread_create (&tid, NULL ,CheckUserBalance, req);
			break;

			case ListAccounts:
			pthread_create (&tid, NULL ,AdminListAccounts, req);
			break;

			case Shutdown:
			pthread_create(&tid, NULL ,ShutdownServer, req);
			break;

		}

	}

	printf("Server shutting down");

	return 0;
}

int * CheckLogin(Request * req)
{
	int i;
	user* temp_user=malloc(sizeof(user));

	for(i=0; i < users->elements; i++)
	{
		if ( ((user *)users->array[i])->id == temp_user->id ) {
			if(strcmp(((user *)users->array[i])->pin,temp_user->pin)>=0)
			{
				return i;
			}
		break;
		}
	}
	return NULL;
}

void *AddAccount(void * req)
{
	sem_wait(sem);
	char *response="Ok\n";

	Request *temp = (Request*)req;
	int fd_ans=Open_Fifo(temp->pid);
	user* temp_user=malloc(sizeof(user));
	temp_user->id=lastID;
	lastID++;
	strcpy(temp_user->name, temp->AccountName);
	strcpy(temp_user->pin,temp->pin);
	temp_user->balance=0;

	addElement(users, temp_user);

	write(fd_ans, response, strlen(response));

	sem_post(sem);
	pthread_exit(NULL);

	return NULL;
}

void *RemoveAccount(void * req)
{
	sem_wait(sem);
	char *response="Ok\n";

	Request *temp = (Request*)req;
	int fd_ans=Open_Fifo(temp->pid);

	if(removeUser(users,temp->AccountNumber)){
		printf("%d\n", strlen(response));
		write(fd_ans, response, strlen(response));
		sem_post(sem);
		pthread_exit(NULL);
		return NULL;}
	else
	{
		response="Invalid account!\n";
		printf("%d\n", strlen(response));
		write(fd_ans, response, strlen(response));
		return NULL;
	}

}

void *UserWithdraw(void * req)
{
	sem_wait(sem);
	Request *temp = (Request*)req;
	int fd_ans=Open_Fifo(temp->pid);
	char *feedback;

	int login=CheckLogin(temp);

	if(login!=NULL)
	{
		if(temp->value <= ((user *)users->array[login])->balance)
		{
			((user *)users->array[login])->balance-=temp->value;
			feedback="Money Withdrawn.\n";
		}
		else
		{
			feedback="You don't have enough money!\n";
		}
		write(fd_ans, feedback, strlen(feedback));

		pthread_exit(NULL);
		return NULL;
	}

	feedback="Incorrect Login! Please, try again\n";
	write(fd_ans, feedback, strlen(feedback));
	sem_post(sem);

	pthread_exit(NULL);
	return NULL;
}

void *UserDeposit(void * req)
{
	sem_wait(sem);
	Request *temp = (Request*)req;
	int fd_ans=Open_Fifo(temp->pid);
	char *feedback;

	int login=CheckLogin(temp);

	if(login!=NULL)
	{
		((user *)users->array[login])->balance+=temp->value;
		feedback="Deposit sucessful.\n";

		write(fd_ans, feedback, strlen(feedback));

		pthread_exit(NULL);
		return NULL;
	}

	feedback="Incorrect Login! Please, try again\n";
	write(fd_ans, feedback, strlen(feedback));
	sem_post(sem);

	pthread_exit(NULL);
	return NULL;
}

void *CheckUserBalance(void * req)
{
	sem_wait(sem);
	Request *temp = (Request*)req;
	int fd_ans=Open_Fifo(temp->pid);
	char *feedback=malloc(512*sizeof(char));

	int login=CheckLogin(temp);

	if(login!=NULL)
	{

		sprintf(feedback,"%d %s %f\n",
				((user *)users->array[login])->id,
				((user *)users->array[login])->name,
				((user *)users->array[login])->balance);

		write(fd_ans, feedback, strlen(feedback));

		pthread_exit(NULL);
		return NULL;
	}

	feedback="Incorrect Login! Please, try again\n";
	write(fd_ans, feedback, strlen(feedback));
	sem_post(sem);

	pthread_exit(NULL);
	return NULL;
}

void *AdminListAccounts(void * req)
{
	sem_wait(sem);
	Request *temp = (Request*)req;
	int i;
	int fd_ans=Open_Fifo(temp->pid);


	sem_post(sem);
	pthread_exit(NULL);
	return NULL;
}

void *ShutdownServer(void * req)
{
	serverStatus=SERVER_OFF;
	sem_wait(sem);
	Request *temp = (Request*)req;
	int fd_ans=Open_Fifo(temp->pid);
	Request *end= malloc(sizeof(Request));
	int fds=open("requests",O_WRONLY);
	end->type=Shutdown;

	write(fds,end, sizeof(Request));

	char *feedback="Server shutdown\n";
	printf("%d\n", strlen(feedback));
	write(fd_ans, feedback, strlen(feedback));
	sem_post(sem);
	pthread_exit(NULL);
	return NULL;
}

